package com.chinasoft;

import lombok.Data;

/**
 * Man类代表游戏中的主角
 * 包含了主角的位置、地图信息以及移动方法
 */
@Data
public class Man {
    // 主角的坐标
    private int manX, manY;
    // 地图数组，表示游戏地图
    private int[][] map;
    // 主角背景，用于记录主角移动前的位置状态
    private int manBk = 2;

    // 创建MapData对象，用于获取地图数据
    public MapData data;


    // 键盘监听器，实现主角移动


    /**
     * 尝试根据指定方向移动
     * 首先检查当前对象是否能够按照参数d指定的方向移动如果可以移动，则调用realMove方法执行实际的移动操作
     * 无法移动时，不会产生任何效果
     *
     * @param d 移动的方向，是一个枚举类型，值包括上、下、左、右等
     */
    public void move(Direction d) {
        Way way = pushOrMove(d);
        System.out.println("way is " + way);
        if (way == Way.Move) {
            if (canMove(d)) {
                realMove(d);
            }
        } else {
            if (canPush(d)) {
                realPush(d);
            }
        }
    }

    /**
     * 根据指定方向实际推动箱子
     * 此方法首先检查是否可以向指定方向推动箱子，如果可以，则获取该方向的箱子并移动它，随后更新玩家的位置
     *
     * @param d 推动的方向，属于Direction枚举类型
     */
    private void realPush(Direction d) {
        // 检查是否可以向指定方向推动箱子
        if (canPush(d)) {
            // 获取指定方向的箱子
            Box box = data.getBoxByDirection(d);
            // 移动箱子
            box.move(d);
            // 更新玩家位置
            this.realMove(d);
        }
    }


    /**
     * 检查是否可以推送
     * 此方法用于判断在特定方向上是否可以进行推送操作
     *
     * @param d 方向枚举，表示要检查的方向
     * @return 如果可以推送返回true，否则返回false
     */
    private boolean canPush(Direction d) {
        // 获取指定方向和距离上的对象
        Integer obj = getDirectObj(d, 2);
        // 如果对象为空，则不能推送
        if (obj == null) {
            return false;
        }

        // 如果对象为2或4，则可以推送
        if (obj == 2 || obj == 4) {
            return true;
        } else {
            // 如果对象不是2或4，则不能推送
            return false;
        }
    }


    /**
     * 根据指定方向判断是推箱子还是移动
     * <p>
     * 此方法用于根据玩家面向的方向判断下一步操作是推箱子还是普通移动
     * 它通过检查玩家面前的对象来决定操作类型如果面前的对象是箱子或含有金子的箱子，
     * 则玩家需要进行推箱子操作；否则，玩家可以进行普通移动
     *
     * @param dire 方向枚举，表示玩家当前面向的方向
     * @return 返回Way枚举类型，表示下一步的操作类型：Push（推箱子）或Move（移动）
     */
    private Way pushOrMove(Direction dire) {
        // 获取当前方向上的对象标识，参数 1 表示检查相邻的下一个位置
        Integer obj = getDirectObj(dire, 1);
        // 3 是箱子， 9 是有金子的箱子
        if (obj == 3 || obj == 9) {
            return Way.Push;
        } else {
            return Way.Move;
        }
    }


    /**
     * 判断当前对象是否可以向指定方向移动
     * getDirectObj方法判断有没有障碍物之后，这个的方法决定是否移动
     * 它通过检查目标方向上的对象来决定是否可以移动
     *
     * @param d 方向枚举，表示当前对象想要移动的方向
     * @return boolean 返回一个布尔值，true表示可以向指定方向移动，false表示不可以
     */
    private boolean canMove(Direction d) {
        // 获取指定方向上的对象
        Integer obj = getDirectObj(d, 1);
        // 如果目标位置的对象为1或为空，则不可以移动
        if (obj == 1 || obj == null) {
            return false;
        } else {
            // 否则，表示可以移动
            return true;
        }
    }


    /**
     * 根据指定方向和距离获取目标位置上的对象
     * (在这里则体现为：看看前方一格有没有障碍物，还是前方一个是否是地图边界，还是可走路径)
     *
     * @param d   方向枚举值，表示从当前位置出发的方向
     * @param num 从当前位置沿指定方向移动的格数,默认为1，
     * @return 目标位置上的对象，如果目标位置超出地图边界或者是碰到障碍物则返回null
     */
    public Integer getDirectObj(Direction d, int num) {
        // 当前位置的横坐标和纵坐标
        int x = manX;
        int y = manY;
        // 初始化目标位置的对象为null
        Integer obj = null;

        // 根据方向枚举值更新目标位置的横纵坐标
        switch (d) {
            case Right:
                x = x + num;
                // 如果更新后的横坐标超出地图右边界(碰到箱子)，则返回null
                if (x >= MapData.WIDTH) {
                    return obj;
                }
                break;
            case Left:
                x = x - num;
                // 如果更新后的横坐标超出地图左边界，则返回null
                if (x < 0) {
                    return obj;
                }
                break;
            case Down:
                y = y + num;
                // 如果更新后的纵坐标超出地图下边界，则返回null
                if (y >= MapData.WIDTH) {
                    return obj;
                }
                break;
            case Up:
                y = y - num;
                // 如果更新后的纵坐标超出地图上边界，则返回null
                if (y < 0) {
                    return obj;
                }
                break;
            default:
                // 如果方向枚举值不是有效的方向，输出错误信息
                System.out.println("unknown situation, please debug it");
                break;
        }
        // 返回目标位置上的对象
        return map[y][x];
    }


    private void realMove(Direction d) {
        map[manY][manX] = manBk;
        switch (d) {
            case Right:
                // 坐标移动
                manX++;
                // 备份目标位置
                manBk = map[manY][manX];
                // 人移动
                map[manY][manX] = 7;
                break;
            case Up:
                manY--;
                manBk = map[manY][manX];
                map[manY][manX] = 8;
                break;
            case Down:
                manY++;
                manBk = map[manY][manX];
                map[manY][manX] = 5;
                break;
            case Left:
                manX--;
                manBk = map[manY][manX];
                map[manY][manX] = 6;
                break;
            default:
                System.out.println("unknown direction in realMove " + d);
                break;
        }
    }
}
